use std::path::PathBuf;

use tera::Value;
use tracing::{info, info_span};

use super::*;
use crate::Result;

pub struct InMemoryCollection {
    index_fields: Vec<String>,
    resource_root: PathBuf,
    /// <key> -> offset in data
    data_index: HashMap<String, usize>,
    data: Vec<Value>,
}

impl InMemoryCollection {
    /// the name of the key filed is expected in `index_fields[0]` 
    pub fn new(data: Vec<Value>, index_fields: Vec<String>, resource_root: PathBuf) -> Self {
        let key = &index_fields[0];
        let data_index = data.iter()
            .enumerate()
            .filter_map(|(n, w)| w.get(key).and_then(stringize_key_value).map(|k| (k, n)))
            .collect();
        Self { index_fields, resource_root, data_index, data }
    }
}

impl CollectionProvider for InMemoryCollection {
    fn get_item(&self, key: &str) -> Fx<Option<Value>> {
        fx_ready(self.data_index.get(key).map(|&pos| self.data[pos].clone()))
    }

    fn get_index_range(&self, range: Range) -> Fx<Vec<Value>> {
        fx_ready(extract_index(&self.data, range, &self.index_fields))
    }
    
    fn get_item_resource(&self, key: &str, path: &str) -> Fx<Option<Resource>> {
        let path = self.resource_root.join(key).join(path);
        fx_ready(if path.is_file() { Some(Resource::File(path)) } else { None })
    }
}

pub fn extract_index(a: &Vec<Value>, range: Range, keys: &[String]) -> Vec<Value> {
    let a = range.project(a);

    let mut rv = Vec::with_capacity(a.len());
    for w in a {
        if let Value::Object(o) = w {
            let mut m = Map::new();
            for k in keys {
                if let Some(v) = o.get(k) { 
                    m.insert(k.clone(), v.clone()); 
                }    
            }
            rv.push(Value::Object(m))
        }
    }
    rv
}



#[derive(Deserialize, Debug)]
pub enum Loader {
    /// Single json file "items.json" with array of item objects
    #[serde(rename = "json")]
    Json { },

    /// Anntotated markdown (directory with .md files)
    #[serde(rename = "md")]
    Md { sort_key: Option<String> }
}

#[derive(Deserialize, Debug)]
pub struct InMemoryCollectionConfig {
    pub loader: Loader,
}

pub fn create_provider(index_fields: Vec<String>, provider_config: toml::Value, provider_data_dir: PathBuf) -> Result<InMemoryCollection> {
    let c: InMemoryCollectionConfig = provider_config.try_into()?;
    let _span_ = info_span!("create_provider", coll_path=?c.loader, provider="memory").entered();

    let data = match c.loader {
        Loader::Json { } => 
            serde_json::from_reader(std::fs::File::open(&provider_data_dir.join("items.json"))?)?,
        Loader::Md { sort_key } => 
            super::md::load_markdown(&provider_data_dir, sort_key.unwrap_or_else(|| "weight".to_string()))?,
    };
    info!(event="collection_loaded", data_len=data.len());
    Ok(InMemoryCollection::new(data, index_fields, provider_data_dir))
}

